import math
import copy

from .monk_base import MonkBaseCalculator

class MonkModelCalculator(MonkBaseCalculator):
    
    TIGER_PALM_BUFF_REFRESH_LIMIT = 3.0
    
    
    def get_enchants_haste_modifier(self):
        ias_wo_ts = self.get_ias_multiplier_without_tiger_strike()
        ias_with_ts = self.get_ias_multiplier_with_tiger_strike()
        spell_haste = self.get_spell_haste_multiplier()
        uptime = self.tiger_strike_uptime()
        
        haste = uptime * max(ias_with_ts, spell_haste) + (1 - uptime) * max(ias_wo_ts, spell_haste)
        return haste
    
    
    def update_stats(self):
        
        haste = self.get_enchants_haste_modifier()
        uptime_last = []
        uptime_new = []
        # update all procs according to current stats
        while not self.close_enough(uptime_last, uptime_new):
            current_stats = copy.copy(self.base_stats)
            uptime_last = copy.copy(uptime_new)
            uptime_new = []
            # update proc
            if not self.stats.mh.enchant == None:
                if self.stats.mh.enchant.name == self.stats.oh.enchant.name:
                    uptime = self.stats.mh.enchant.get_uptime(self.get_attack_per_second_melee_land(), haste, dw_same=True)
                    uptime_new.append(uptime)
                    proc_stats = self.stats.mh.enchant.get_stats_from_enchant()
                    for stat in proc_stats:
                        attr = current_stats.__getattribute__(stat) 
                        current_stats.__setattr__(stat, (attr +  proc_stats.get(stat) * uptime))
                else:
                    # mainhand
                    uptime = self.stats.mh.enchant.get_uptime(self.get_attack_per_second_melee_land(), haste)
                    uptime_new.append(uptime)
                    proc_stats = self.stats.mh.enchant.get_stats_from_enchant()
                    for stat in proc_stats:
                        attr = current_stats.__getattribute__(stat) 
                        current_stats.__setattr__(stat, (attr +  proc_stats.get(stat) * uptime))
                        
                    # offhand
                    if not self.stats.oh.enchant == None:         
                        uptime = self.stats.oh.enchant.get_uptime(self.get_attack_per_second_melee_land(), haste)
                        uptime_new.append(uptime)
                        proc_stats = self.stats.oh.enchant.get_stats_from_enchant()
                        for stat in proc_stats:
                            attr = current_stats.__getattribute__(stat) 
                            current_stats.__setattr__(stat, (attr +  proc_stats.get(stat) * uptime))
            # No weapon enchant              
            else:
                uptime_new.append(0)
            
            self.stats = copy.copy(current_stats)
            self.update_base_stats_by_buffs()
            self.update_white_tiger_stats()
            self.get_spell_crit_chance()
            
    
    def close_enough(self, last, current):
        size = last.__len__()
        # TODO
        if size == 0:
            return False
        else:
            for x in xrange(size):
                if abs(last[x] - current[x]) > .0001:
                    return False
                    
        return True
    
        
    def get_dps(self):
        self.stats = copy.copy(self.base_stats)
        
        self.update_stats()
        dps = self.get_total_damage() / self.settings.combat_time        
        
        return dps
    
    
    def get_total_damage(self):
        damage = 0
        # Autoattack
        damage += self.get_total_auto_attack_damage();
        
        # Spell
        crit_bonus = self.crit_damage_rate() - 1
        
        # Tiger Strike
        hit_count = self.tiger_strike_count('MH') * self.spell_hit_chance('TIGER_STRIKE_MH')
        damage += hit_count * self.auto_attack_hit_damage('MH') * (1 + self.crit_chance.get('TIGER_STRIKE_MH') * crit_bonus)
        hit_count = self.tiger_strike_count('OH') * self.spell_hit_chance('TIGER_STRIKE_MH')
        damage += hit_count * self.auto_attack_hit_damage('OH') * (1 + self.crit_chance.get('TIGER_STRIKE_OH') * crit_bonus)
        # Jab
        hit_count = self.get_jab_count() * self.spell_hit_chance('JAB')
        damage += hit_count * self.spell_hit_damage('JAB') * (1 + self.crit_chance.get('JAB') * crit_bonus)
        # Rising Sun Kick
        hit_count = self.get_rising_sun_kick_count() * self.spell_hit_chance('RISING_SUN_KICK')
        damage += hit_count * self.spell_hit_damage('RISING_SUN_KICK') * (1 + self.crit_chance.get('RISING_SUN_KICK') * crit_bonus)
        # Fist of Fury
        hit_count = self.get_fists_of_fury_count() * self.spell_hit_chance('FISTS_OF_FURY') * self.FISTS_OF_FURY_TICK_NUMBER
        damage += hit_count * self.spell_hit_damage('FISTS_OF_FURY') * (1 + self.crit_chance.get('FISTS_OF_FURY') * crit_bonus)
        # Tiger Palm
        hit_count = self.get_tiger_palm_count_cost()
        hit_count += self.get_tiger_palm_count_free() * self.spell_hit_chance('TIGER_PALM')
        damage += hit_count * self.spell_hit_damage('TIGER_PALM') * (1 + self.crit_chance.get('TIGER_PALM') * crit_bonus)
        # Blackout Kick (including 20% DoT)
        hit_count = self.get_blackout_kick_count_cost()
        hit_count += self.get_blackout_kick_count_free() * self.spell_hit_chance('BLACKOUT_KICK')
        damage += hit_count * self.spell_hit_damage('BLACKOUT_KICK') * (1 + self.crit_chance.get('BLACKOUT_KICK') * crit_bonus) * (1 + self.MONK_SPELLS.get('BLACKOUT_KICK_DOT').get('COEFFICIENT'))
        # Spinning Fire Blossom
        hit_count = self.get_spinning_fire_blossom_count()
        damage += hit_count * self.spell_hit_damage('SPINNING_FIRE_BLOSSOM') * (1 + self.crit_chance.get('SPINNING_FIRE_BLOSSOM') * crit_bonus)     
                    
        # Stance of Fierce Tiger 20%
        damage *= self.STANCE_COEFFICENT
        
        # Rising Sun Kick 15%
        damage *= self.RISING_SUN_KICK_DAMAGE_BONUS_MULTIPLIER
        
        # Tiger Eye Brew
        damage *= self.get_tiger_eye_brew_multiplier()
        
        # Pet
        self.update_white_tiger_stats()
        damage += self.get_white_tiger_auto_attack_total_damage()
        damage += self.get_white_tiger_lightning_tick_count() * self.white_tiger_lightning_hit_chance() * self.white_tiger_lightning_tick_damage() * (1 + self.pet.crit * crit_bonus)
        
        return damage
    
    
    def get_attack_per_second_spell_hit(self):
        aps = 0
        
        aps += self.get_spinning_fire_blossom_count()
        # TODO
        
        return aps
        
        
    def get_attack_per_second_melee_land(self):
        aps = 0 
        # auto attack
        aps += self.auto_attack_count('MH') * self.auto_attack_land_chance('MH')
        aps += self.auto_attack_count('OH') * self.auto_attack_land_chance('OH')
        # Tiger Strike
        aps += self.tiger_strike_count('MH') * self.spell_hit_chance('TIGER_STRIKE_MH')
        aps += self.tiger_strike_count('OH') * self.spell_hit_chance('TIGER_STRIKE_OH')
        
        # spell
        aps += self.get_jab_count() * self.spell_hit_chance('JAB')
        aps += self.get_rising_sun_kick_count() * self.spell_hit_chance('RISING_SUN_KICK')
        
        aps += self.get_fists_of_fury_count() * self.spell_hit_chance('FISTS_OF_FURY') * self.FISTS_OF_FURY_TICK_NUMBER
        aps += self.get_tiger_palm_count_cost()
        aps += self.get_tiger_palm_count_free() * self.spell_hit_chance('TIGER_PALM')
        aps += self.get_blackout_kick_count_cost()
        aps += self.get_blackout_kick_count_free() * self.spell_hit_chance('BLACKOUT_KICK')
        
        aps /= self.settings.combat_time
        
        return aps
    
    
    def get_attack_per_second_melee_crit(self):
        cps = 0   
        # auto attack
        cps += self.auto_attack_count('MH') * self.crit_chance.get('AUTO_ATTACK_MH')
        cps += self.auto_attack_count('OH') * self.crit_chance.get('AUTO_ATTACK_OH')
        # Tiger Strike
        cps += self.tiger_strike_count('MH') * self.spell_hit_chance('TIGER_STRIKE_MH') * self.crit_chance.get('TIGER_STRIKE_MH')
        cps += self.tiger_strike_count('OH')  * self.spell_hit_chance('TIGER_STRIKE_OH') * self.crit_chance.get('TIGER_STRIKE_OH')
        
        # spell
        cps += self.get_jab_count(self) * self.spell_hit_chance('JAB') * self.crit_chance.get('JAB')
        cps += self.get_rising_sun_kick_count() * self.spell_hit_chance('RISING_SUN_KICK') * self.crit_chance.get('RISING_SUN_KICK')
        
        cps += self.get_fists_of_fury_count() * self.spell_hit_chance('FISTS_OF_FURY') * self.FISTS_OF_FURY_TICK_NUMBER * self.crit_chance.get('FISTS_OF_FURY')
        cps += self.get_tiger_palm_count_cost() * self.crit_chance.get('TIGER_PALM')
        cps += self.get_tiger_palm_count_free() * self.spell_hit_chance('TIGER_PALM') * self.crit_chance.get('TIGER_PALM')
        cps += self.get_blackout_kick_count_cost() * self.crit_chance.get('BLACKOUT_KICK')
        cps += self.get_blackout_kick_count_free() * self.spell_hit_chance('BLACKOUT_KICK') * self.crit_chance.get('BLACKOUT_KICK')
        
        cps /= self.settings.combat_time
        
        return cps
    
    
    def auto_attack_count(self, hand):
        if hand == 'MH':
            weapon_speed = self.stats.mh.speed
        elif hand == 'OH':
            if self.is_two_hand_weapon():
                return 0
            else:
                weapon_speed = self.stats.oh.speed
        # Auto attack stop when channeling Fist of Fury
        auto_attack_time = self.settings.combat_time - self.get_fists_of_fury_count() * self.fists_of_fury_channel_time()
        
        attack_number = auto_attack_time / weapon_speed * self.get_ias_multiplier()
        return attack_number
    
    
    def get_total_auto_attack_damage(self):
        total_damage = 0  
        # mainhand
        attack_count = self.auto_attack_count('MH')
        hit_damage = self.auto_attack_hit_damage('MH')
        crit_chance = self.crit_chance.get('AUTO_ATTACK_MH')
        # glance
        total_damage += attack_count * self.GLANCE_CHANCE * hit_damage * self.GLANCE_DAMAGE_RATE
        # crit
        total_damage += attack_count * crit_chance * hit_damage * self.crit_damage_rate()
        # hit
        hit_chance = max(0, self.auto_attack_land_chance('MH') - self.GLANCE_CHANCE - crit_chance)
        total_damage += attack_count * hit_chance * hit_damage
        
        #offhand
        if self.is_dual_wield():
            attack_count = self.auto_attack_count('OH')
            hit_damage = self.auto_attack_hit_damage('OH')
            crit_chance = self.crit_chance.get('AUTO_ATTACK_OH')
            # glance
            total_damage += attack_count * self.GLANCE_CHANCE * hit_damage * self.GLANCE_DAMAGE_RATE
            # crit
            total_damage += attack_count * crit_chance * hit_damage * self.crit_damage_rate()
            # hit
            hit_chance = max(0, self.auto_attack_land_chance('OH') - self.GLANCE_CHANCE - crit_chance)
            total_damage += attack_count * hit_chance * hit_damage
            
        return total_damage
    
    
    def get_energizing_brew_last_time(self):
        cd = self.ENERGIZING_BREW_BASE_LAST_TIME
        cd += self.stats.is_monk_4T14_windwalker() * self.MONK_4T14_WINDWALKER_EB_DURATION_INCREASE
        return cd
          
            
    def get_total_energy(self):
        # get haste
        haste = self.haste_multiplier_total()
        energy_regen = self.BASE_ENERGY_REGEN_PER_SECOND * haste * self.settings.combat_time
        if self.settings.talents.level_45 == 'ASCENSION':
            energy_regen *= self.ASCENSION_ENERGY_REGEN_RATE
        # energy brew
        energy_brew_time = self.get_spell_uptime(self.get_energizing_brew_last_time(), self.ENERGIZING_BREW_CD) * self.settings.combat_time
        energy_regen += energy_brew_time * self.ENERGIZING_BREW_ENERGY_GAIN_PER_SECOND
        
        return self.ENERGY_POOL_SIZE / 2 + energy_regen
    
    
    def get_jab_count(self):
        # the expectation of energy considering miss refund 
        energy_cost = self.JAB_ENERGY_COST * (1 - (1 - self.spell_hit_chance('JAB')) * (1 - self.ENERGY_REFUND_PERCENTAGE_MISS))
        jab_count = self.get_total_energy() / energy_cost
        return jab_count
    
    
    def get_total_chi(self):
        base_chi = self.get_jab_count() * self.spell_hit_chance('JAB') * self.JAB_CHI_GAIN
        # check talent:
        if self.settings.talents.level_45 == 'CHI_BREW':
            chi_from_talent = math.ceil(self.settings.combat_time / self.CHI_BREW_CD) * self.CHI_BREW_CHI_GAIN
        elif self.settings.talents.level_45 == 'POWER_STRIKE':
            chi_from_talent = math.ceil(self.settings.combat_time / self.POWER_STRIKE_CD) * self.POWER_STRIKE_ADDITIONAL_CHI_GAIN
        else:
            chi_from_talent = 0
             
        return base_chi + chi_from_talent
    
    
    # TODO
    def get_rising_sun_kick_average_cd(self):
        cd = self.RISING_SUN_KICK_CD
        if self.settings.rotation.delay_rsk_for_bk:
            cd += cd / self.get_fists_of_fury_average_cd() * self.get_mastery_chance_windwalker() / self.spell_hit_chance('BLACKOUT_KICK')
        return cd
    
    
    def get_rising_sun_kick_count(self):
        return self.settings.combat_time / self.get_rising_sun_kick_average_cd()
    
    
    def get_fists_of_fury_average_cd(self):
        cd = self.FISTS_OF_FURY_CD - self.stats.is_monk_2T14_windwalker() * self.MONK_2T14_WINDWALKER_FOF_CD_REDUCTION
        cd += self.RISING_SUN_KICK_CD / 2 + 1
        cd += self.RISING_SUN_KICK_CD * self.ENERGIZING_BREW_BASE_LAST_TIME / self.ENERGIZING_BREW_CD + 1
        # tp_buff_off_chance = self.fists_of_fury_channel_time()  * self.get_tiger_palm_buff_off_chance(self.fists_of_fury_channel_time())
        
        return cd
    
    
    def get_fists_of_fury_count(self):
        # get setting
        if self.settings.rotation.use_fists_of_fury:
            average_cd = self.get_fists_of_fury_average_cd()
            count = self.settings.combat_time / average_cd
        else:
            count = 0
        return count
    
    
    def get_tiger_palm_buff_off_chance(self, time_limit=TIGER_PALM_BUFF_REFRESH_LIMIT):
        refresh_time = self.TIGER_PALM_BUFF_LENGTH - time_limit
        # get jab per second
        jab_count = self.get_jab_count()
        jab_per_second = jab_count * self.spell_hit_chance('JAB') / self.settings.combat_time
        # get mastery
        mastery = self.get_mastery_chance_windwalker()
              
        off_chance = pow(1-mastery, jab_per_second*refresh_time)
        return off_chance
    
    
    # let P donate the chance that monk need to refresh tiger palm buff by CHI
    # P = off chance by free * (P + (1 - P) * mastery chance * jab per second)
    def get_tiger_palm_count_cost(self):
        off_chance = self.get_tiger_palm_buff_off_chance()
        # get mastery
        mastery = self.get_mastery_chance_windwalker()
        # get jab per second
        jab_count = self.get_jab_count()
        jab_per_second = jab_count * self.spell_hit_chance('JAB') / self.settings.combat_time
        
        free_proc_per_second = jab_per_second * mastery
        
        chance_to_cast = off_chance * free_proc_per_second / (1 - off_chance + off_chance * free_proc_per_second) 
        count_refresh = chance_to_cast * self.settings.combat_time
        
        tiger_palm_count_opener = self.TIGER_PALM_MAX_STACK * (1 - mastery)
        count =  count_refresh + tiger_palm_count_opener
        
        return count
    
    
    def get_tiger_palm_count_free(self):
        # get mastery
        mastery = self.get_mastery_chance_windwalker()
        count_free = self.get_jab_count() * mastery
        
        # get setting
        if self.settings.rotation.use_fists_of_fury:
            count_free -= self.get_fists_of_fury_count() * pow(mastery, 2)
            
        return count_free
    
    
    def get_tiger_palm_count_total(self):
        count = self.get_tiger_palm_count_free() + self.get_tiger_palm_count_cost()
        return count
    
    
    def get_blackout_kick_count_cost(self):
        chi_available = self.get_total_chi()
        chi_available -= self.get_rising_sun_kick_count() * self.spell_hit_chance('RISING_SUN_KICK') * self.RISING_SUN_KICK_CHI_COST
        chi_available -= self.get_fists_of_fury_count() * self.FISTS_OF_FURY_CHI_COST
        chi_available -= self.get_tiger_palm_count_cost() * self.TIGER_PALM_CHI_COST
        
        count = chi_available / self.BLACKOUT_KICK_CHI_COST
        
        return count
    
    
    def get_blackout_kick_count_free(self):
        # get mastery
        mastery = self.get_mastery_chance_windwalker()
        count_free = self.get_jab_count() * mastery
        
        if self.settings.rotation.use_fists_of_fury and not self.settings.rotation.delay_rsk_for_bk:
            count_free -= self.get_fists_of_fury_count() * pow(mastery, 2)
            
        return count_free
    
    
    def get_blackout_kick_count_total(self):
        count = self.get_blackout_kick_count_free() + self.get_blackout_kick_count_cost()
        return count
    
    
    def get_spinning_fire_blossom_count(self):
        # placeholder
        # TODO
        return 0
    
    
    def get_white_tiger_active_time(self):
        if self.settings.talents.level_90 == 'WHITE_TIGER':
            count_full = math.floor(self.settings.combat_time / self.WHITE_TIGER_CD)
            time_full = count_full * (self.WHITE_TIGER_LAST_TIME - self.WHITE_TIGER_HESITATION_TIME)
            time_left = self.settings.combat_time - count_full * self.WHITE_TIGER_CD
            time_total = time_full +  max(0, min(time_left, self.WHITE_TIGER_LAST_TIME) - self.WHITE_TIGER_HESITATION_TIME)
        else:
            time_total = 0
        
        return time_total
    
    
    def get_white_tiger_auto_attack_count(self):
        time = self.get_white_tiger_active_time()
        return time / self.pet.attack_speed * self.pet.ias
    
    
    def get_white_tiger_lightning_tick_count(self):
        time = self.get_white_tiger_active_time()
        return time / self.WHITE_TIGER_LIGHTNING_CD * self.WHITE_TIGER_LIGHTNING_TICK
    
    
    def get_white_tiger_auto_attack_total_damage(self):
        total_damage = 0
        attack_count = self.get_white_tiger_auto_attack_count()
        hit_damage = self.white_tiger_auto_attack_hit_damage()
        crit_chance = self.pet.crit
        # glance
        total_damage += attack_count * self.GLANCE_CHANCE * hit_damage * self.GLANCE_DAMAGE_RATE
        # crit
        total_damage += attack_count * crit_chance * hit_damage * self.crit_damage_rate()
        # hit
        hit_chance = max(0, self.white_tiger_auto_attack_land_chance() - self.GLANCE_CHANCE - crit_chance)
        total_damage += attack_count * hit_chance * hit_damage
        
        return total_damage
    
    
    def get_white_tiger_dps(self):
        damage = 0
        crit_bonus = self.crit_damage_rate()
        self.update_white_tiger_stats()
        damage += self.get_white_tiger_auto_attack_total_damage()
        damage += self.get_white_tiger_lightning_tick_count() * self.white_tiger_lightning_hit_chance() * self.white_tiger_lightning_tick_damage() * (1 + self.pet.crit * crit_bonus)
        
        return damage/self.get_white_tiger_active_time(), damage/self.settings.combat_time
    
    
    def get_tiger_eye_brew_multiplier(self):
        # Combo : Tiger Palm bug
        stack = (self.get_total_chi() - self.get_tiger_palm_count_free() * self.TIGER_PALM_CHI_COST) / self.TIGER_EYE_CHI_COST_PER_STACK
        uptime = self.TIGER_EYE_LAST_TIME / self.settings.combat_time
        bonus = stack * self.TIGER_EYE_DAMAGE_BONUS_PER_STACK
        
        return (1 + uptime * bonus)
    
    
    def get_damage_loss_delaying_rsk(self, time):
        crit_bonus = self.crit_damage_rate() - 1
        damage = self.spell_hit_damage('RISING_SUN_KICK') * self.spell_hit_chance('RISING_SUN_KICK') * (1 + self.crit_chance.get('RISING_SUN_KICK') * crit_bonus)
        damage -= self.spell_hit_damage('BLACKOUT_KICK') * (1 + self.crit_chance.get('BLACKOUT_KICK') * crit_bonus) * (1 + self.MONK_SPELLS.get('BLACKOUT_KICK_DOT').get('COEFFICIENT'))
        
        return damage * time / self.RISING_SUN_KICK_CD
    
    
    def get_damage_loss_overlapping_free_bk(self):
        crit_bonus = self.crit_damage_rate() - 1
        damage = self.spell_hit_damage('BLACKOUT_KICK') * (1 + self.crit_chance.get('BLACKOUT_KICK') * crit_bonus) * (1 + self.MONK_SPELLS.get('BLACKOUT_KICK_DOT').get('COEFFICIENT'))
        return damage * self.get_mastery_chance_windwalker()
    
